package io.noties.markwon.base

import java.lang.NullPointerException
import java.util.HashMap

/**
 * Class to hold data in [io.noties.markwon.base.RenderProps]. Represents a certain *property*.
 *
 * @param <T> represents the type that this instance holds
 * @see .of
 * @see .of
 * @since 3.0.0
</T> */
class Prop<T> internal constructor(private val name: String) {
    fun name(): String {
        return name
    }

    operator fun get(props: RenderProps): T? {
        return props.get(this)
    }

    operator fun get(props: RenderProps, defValue: T): T {
        return props.get(this, defValue)
    }

    fun require(props: RenderProps): T {
        return get(props) ?: throw NullPointerException(name)
    }

    operator fun set(props: RenderProps, value: T?) {
        props.set(this, value)
    }

    fun clear(props: RenderProps) {
        props.clear(this)
    }

    override fun equals(o: Any?): Boolean {
        if (this === o) return true
        if (o == null || javaClass != o.javaClass) return false
        val prop = o as Prop<*>
        return name == prop.name
    }

    override fun hashCode(): Int {
        return name.hashCode()
    }

    override fun toString(): String {
        return "Prop{" +
                "name='" + name + '\'' +
                '}'
    }

    companion object {
        fun <T> of(type: Class<T>, name: String): Prop<T> {
            return Prop(name)
        }

        fun <T> of(name: String): Prop<T> {
            return Prop(name)
        }
    }
}


/**
 * @since 3.0.0
 */
interface RenderProps {
    operator fun <T> get(prop: Prop<T>): T?
    operator fun <T> get(prop: Prop<T>, defValue: T): T
    operator fun <T> set(prop: Prop<T>, value: T?)
    fun <T> clear(prop: Prop<T>)
    fun clearAll()
}

/**
 * @since 3.0.0
 */
internal class RenderPropsImpl : RenderProps {
    private val values: MutableMap<Prop<*>, Any> = HashMap(3)
    override fun <T> get(prop: Prop<T>): T? {
        return values[prop] as T?
    }

    override fun <T> get(prop: Prop<T>, defValue: T): T {
        val value = values[prop]
        return if (value != null) {
            value as T
        } else defValue
    }

    override fun <T> set(prop: Prop<T>, value: T?) {
        if (value == null) {
            values.remove(prop)
        } else {
            values[prop] = value
        }
    }

    override fun <T> clear(prop: Prop<T>) {
        values.remove(prop)
    }

    override fun clearAll() {
        values.clear()
    }
}